/**
 * Computer Graphics Lecture WS 2009/2010 Ulm University
 * Creator: Manuel Finckh
 * Email:   manuel.finckh@uni-ulm.de
 */

#ifndef MATERIAL_H
#define MATERIAL_H

#include "utils/vec.h"
#include "utils/MersenneTwister.h"
#include "rtStructs.h"
#include <iostream>
#include <string>



struct Texture {
  int ResX, ResY;
  Vec3 *data; // asumes rgb texture

  Texture(const int ResX, const int ResY, Vec3 *data) : ResX(ResX), ResY(ResY), data(data) { }

  Vec3 getColor(Vec2 &coords) {
    coords[0] = fmod(coords[0], 1.0f);
    coords[1] = -fmod(coords[1], 1.0f);
    if ( coords[0] < 0.0f ) coords[0] += 1.0f;
    if ( coords[1] < 0.0f ) coords[1] += 1.0f;

    int c0 = int(coords[0] * (float)ResX);
    int c1 = int(coords[1] * (float)ResY);

    c0 = c0 >= 0 ? c0 : 0;
    c1 = c1 >= 0 ? c1 : 0;

    c0 = c0 < ResX ? c0 : ResX - 1;
    c1 = c1 < ResY ? c1 : ResY - 1;

    return data[c0 + c1 * ResX];
  }
};

struct Light{
	int index;
	float area;
	
	Light(){};
	Light(int _index, float _area){
		index = _index;
		area = _area;
	}
};

struct Material {
  Vec3 color_d; // diffuse color
  Vec3 color_s; // specular color (not needed for the exercise)
  Vec3 color_t; // trans color (not needed for the exercise)
  Vec3 color_e; // emmit color

  Texture * tex;
  
  string name;
  

  float hard, eta, attd; // hard = 0.0f -> diffuse, hard = 1.0f -> specular
  int start, end, flags;
  

  Material() {};
  Material(const Vec3 &d, const Vec3 &s, const Vec3 &t, const Vec3 &e, float hard, float eta, float attd, Texture *tex, int start, int end) : tex(tex), hard(hard), eta(eta), attd(attd), start(start), end(end) {

    color_d = d;
    color_s = s;
    color_t = t;
    color_e = e;
  }

  inline static void diffuse(Vec3 &out, const Vec3 &n, const float t0, const float t1);
  inline static void specular(Vec3 &out, const Vec3 &in, const Vec3 &n);
  inline static void glass(Vec3 &out, const Vec3 &in, const Vec3 &n, const float &r);
  inline static float schlick(const Vec3 &in, const Vec3 &n);
  inline static void fresnel(float &R, float &T, const Vec3 &in, const Vec3 &n, float n1, float n2);
  
};

inline void Material::diffuse(Vec3 &out, const Vec3 &n, const float t0, const float t1) {
  Vec3 u, v, w;
  Vec3::onb(u, v, w, n);

  const float phi = 2.0f * M_PI * t0;
  const float sqrtu = sqrtf(t1);
  const float x = sqrtu * cosf(phi);
  const float y = sqrtu * sinf(phi);
  const float z = sqrtf(1.0f - t1);

  out = x * u + y * v + z * w;
}

inline void Material::specular(Vec3 &out, const Vec3 &in, const Vec3 &n) {
  const float dot2 = 2.0f * (in * n); // assumes in points onto surface -> (in * normal < 0.0f)

  out = in - (dot2 * n);
}

inline void Material::glass(Vec3 &out, const Vec3 &in, const Vec3 &n, const float &r){
	
	float n1 = 1.0;
  	float n2 = 1.5;
	
	float R, T;
	if(in * n > 0.0)
		fresnel(R,T,in,-n,n2,n1);
	else
		fresnel(R,T,in,n,n1,n2);
		
	if(r < R)
		if(in * n > 0.0)
			specular(out, -in, n);
		else
			specular(out, in, n);
	else{
		float n0 = n1 / n2;
		float c1 = n * (-in);
		float c2 = sqrt(1 - (n0 * n0) * (1-(c1*c1)));
		if(c1 > 0)
			out = n0 * in + (n0 * c1 - c2) * n;
		else
			out = n0 * in + (n0 * c1 + c2) * n;
	}
			
}
// n1 = actual medium, n2 = adjacent one
inline void Material::fresnel(float &R, float &T, const Vec3 &in, const Vec3 &n, float n1, float n2){

	float n0 = n1/n2;
	float c_i = (-in) * n;
	float s_t = n0 * n0 * (1.0 - c_i*c_i);
	float c_t = sqrtf(1.0 - s_t);
	
	if(s_t > 1.0){
		R = 1.0;
		T = 0.0;
		return;
	}	
	
	float R_r = (n1 * c_i - n2 * c_t) / (n1 * c_i + n2 * c_t);
	float R_t = (n2 * c_i - n1 * c_t) / (n2 * c_i + n1 * c_t);
	
	R = 0.5 * (R_r * R_r + R_t * R_t);
	T = 1.0 - R; 
	


}

inline float Material::schlick(const Vec3 &in, const Vec3 &n){
	float n1 = 1.0;
	float n2 = 1.33;

	float theta = fabs(in * n);
	float r0 = ((n1 - n2) / (n1 + n2)) * ((n1 - n2) / (n1 + n2));
	
	return (r0 + (1.0-r0) * pow((1-cos(theta)),5));

}

#endif

